//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <gmock/gmock.h>

#include <sqbind/sqbTypeInfo.h>

#include "fixtures/SquirrelFixture.h"
#include "TypeHelpers.h"
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
/// RawTypeToTypeName tests
//----------------------------------------------------------------------------------------------------------------------
class RawTypeToTypeNameTest : public ::testing::TestWithParam<SQObjectType>
{
};

//----------------------------------------------------------------------------------------------------------------------
/// \brief calls typeof for the value at index in the stack and compares the result to actual.
//----------------------------------------------------------------------------------------------------------------------
TEST_P(RawTypeToTypeNameTest, TestTypeOf)
{
  HSQUIRRELVM vm = sq_open(128);

  // compile a function that will invoke typeof on the first argument to this function and return the result
  //
  const SQChar* buffer = _SC("return typeof(vargv[0])");
  SQInteger buffer_size = scstrlen(buffer);
  ASSERT_SQ_SUCCEEDED(vm, sq_compilebuffer(vm, buffer, buffer_size, _SC("buffer"), SQFalse));

  // call the function leaving the result on the top of the stack.
  //
  sq_pushroottable(vm);
  EXPECT_NO_FATAL_FAILURE(PushObjectOfType(vm, GetParam()));
  ASSERT_SQ_SUCCEEDED(vm, sq_call(vm, 2, SQTrue, SQFalse));

  // compare the result of typeof to actual
  //
  const SQChar* expected = _SC("");
  ASSERT_SQ_SUCCEEDED(vm, sq_getstring(vm, -1, &expected));

  const SQChar* actual = sqb::RawTypeToTypeName(GetParam());
  EXPECT_STREQ(expected, actual);
};

INSTANTIATE_TEST_CASE_P(TestAllTypes, RawTypeToTypeNameTest, ::testing::ValuesIn(kSquirrelAllTypes));

//----------------------------------------------------------------------------------------------------------------------
/// \brief Have to check weakref on its own due to complications with pushing it as a type
//----------------------------------------------------------------------------------------------------------------------
TEST_F(RawTypeToTypeNameTest, WeakRefTypeOfTest)
{
  HSQUIRRELVM vm = sq_open(128);

  // compile a function that will return the result of typeof(weakref).
  //
  const SQChar* buffer = _SC("local t = { } return typeof(t.weakref())");
  SQInteger buffer_size = scstrlen(buffer);
  ASSERT_SQ_SUCCEEDED(vm, sq_compilebuffer(vm, buffer, buffer_size, _SC("buffer"), SQFalse));

  // call the function leaving the result on the top of the stack.
  //
  sq_pushroottable(vm);
  ASSERT_SQ_SUCCEEDED(vm, sq_call(vm, 1, SQTrue, SQFalse));

  // compare the result of typeof to actual
  //
  const SQChar* expected = _SC("");
  ASSERT_SQ_SUCCEEDED(vm, sq_getstring(vm, -1, &expected));

  const SQChar* actual = sqb::RawTypeToTypeName(OT_WEAKREF);
  EXPECT_STREQ(expected, actual);
}

//----------------------------------------------------------------------------------------------------------------------
/// \brief Check in invalid argument to sqb::RawTypeToTypeName.
//----------------------------------------------------------------------------------------------------------------------
TEST_F(RawTypeToTypeNameTest, InvalidArgument)
{
  const SQChar* actual = sqb::RawTypeToTypeName(static_cast<SQObjectType>(0xffffffff));
  EXPECT_STREQ(_SC(""), actual);
}

//----------------------------------------------------------------------------------------------------------------------
/// TypeInfo tests
//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestUnboundType)
{
  EXPECT_EQ(sqb::kScriptVarTypeNone, sqb::TypeInfo<UnboundClass>::kTypeID);
  EXPECT_EQ(sizeof(UnboundClass), sqb::TypeInfo<UnboundClass>::kTypeSize);
  EXPECT_EQ(_SC('?'), sqb::TypeInfo<UnboundClass>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<UnboundClass>::kTypeIsInstance);
  EXPECT_STREQ(_SC(""), sqb::TypeInfo<UnboundClass>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestBool)
{
  EXPECT_EQ(sqb::kScriptVarTypeBool, sqb::TypeInfo<bool>::kTypeID);
  EXPECT_EQ(sizeof(bool), sqb::TypeInfo<bool>::kTypeSize);
  EXPECT_EQ(_SC('b'), sqb::TypeInfo<bool>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<bool>::kTypeIsInstance);
  EXPECT_STREQ(_SC("bool"), sqb::TypeInfo<bool>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestChar)
{
  EXPECT_EQ(sqb::kScriptVarTypeChar, sqb::TypeInfo<char>::kTypeID);
  EXPECT_EQ(sizeof(char), sqb::TypeInfo<char>::kTypeSize);
  EXPECT_EQ(_SC('i'), sqb::TypeInfo<char>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<char>::kTypeIsInstance);
  EXPECT_STREQ(_SC("integer"), sqb::TypeInfo<char>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestInt8)
{
  EXPECT_EQ(sqb::kScriptVarTypeInt8, sqb::TypeInfo<int8_t>::kTypeID);
  EXPECT_EQ(sizeof(int8_t), sqb::TypeInfo<int8_t>::kTypeSize);
  EXPECT_EQ(_SC('i'), sqb::TypeInfo<int8_t>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<int8_t>::kTypeIsInstance);
  EXPECT_STREQ(_SC("integer"), sqb::TypeInfo<int8_t>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestUInt8)
{
  EXPECT_EQ(sqb::kScriptVarTypeUInt8, sqb::TypeInfo<uint8_t>::kTypeID);
  EXPECT_EQ(sizeof(uint8_t), sqb::TypeInfo<uint8_t>::kTypeSize);
  EXPECT_EQ(_SC('i'), sqb::TypeInfo<uint8_t>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<uint8_t>::kTypeIsInstance);
  EXPECT_STREQ(_SC("integer"), sqb::TypeInfo<uint8_t>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestInt16)
{
  EXPECT_EQ(sqb::kScriptVarTypeInt16, sqb::TypeInfo<int16_t>::kTypeID);
  EXPECT_EQ(sizeof(int16_t), sqb::TypeInfo<int16_t>::kTypeSize);
  EXPECT_EQ(_SC('i'), sqb::TypeInfo<int16_t>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<int16_t>::kTypeIsInstance);
  EXPECT_STREQ(_SC("integer"), sqb::TypeInfo<int16_t>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestUInt16)
{
  EXPECT_EQ(sqb::kScriptVarTypeUInt16, sqb::TypeInfo<uint16_t>::kTypeID);
  EXPECT_EQ(sizeof(uint16_t), sqb::TypeInfo<uint16_t>::kTypeSize);
  EXPECT_EQ(_SC('i'), sqb::TypeInfo<uint16_t>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<uint16_t>::kTypeIsInstance);
  EXPECT_STREQ(_SC("integer"), sqb::TypeInfo<uint16_t>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestInt32)
{
  EXPECT_EQ(sqb::kScriptVarTypeInt32, sqb::TypeInfo<int32_t>::kTypeID);
  EXPECT_EQ(sizeof(int32_t), sqb::TypeInfo<int32_t>::kTypeSize);
  EXPECT_EQ(_SC('n'), sqb::TypeInfo<int32_t>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<int32_t>::kTypeIsInstance);
  EXPECT_STREQ(_SC("integer"), sqb::TypeInfo<int32_t>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestUInt32)
{
  EXPECT_EQ(sqb::kScriptVarTypeUInt32, sqb::TypeInfo<uint32_t>::kTypeID);
  EXPECT_EQ(sizeof(uint32_t), sqb::TypeInfo<uint32_t>::kTypeSize);
  EXPECT_EQ(_SC('n'), sqb::TypeInfo<uint32_t>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<uint32_t>::kTypeIsInstance);
  EXPECT_STREQ(_SC("integer"), sqb::TypeInfo<uint32_t>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestFloat)
{
  EXPECT_EQ(sqb::kScriptVarTypeFloat, sqb::TypeInfo<float>::kTypeID);
  EXPECT_EQ(sizeof(float), sqb::TypeInfo<float>::kTypeSize);
  EXPECT_EQ(_SC('n'), sqb::TypeInfo<float>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<float>::kTypeIsInstance);
  EXPECT_STREQ(_SC("float"), sqb::TypeInfo<float>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestString)
{
  EXPECT_EQ(sqb::kScriptVarTypeString, sqb::TypeInfo<const SQChar *>::kTypeID);
  EXPECT_EQ(sizeof(const SQChar *), sqb::TypeInfo<const SQChar *>::kTypeSize);
  EXPECT_EQ(_SC('s'), sqb::TypeInfo<const SQChar *>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<const SQChar *>::kTypeIsInstance);
  EXPECT_STREQ(_SC("string"), sqb::TypeInfo<const SQChar *>().m_typeName);

  // check that removing the qualifiers for const SQChar* still correctly identifies
  // it as a string when used with TypeInfo.
  EXPECT_EQ(_SC('s'), sqb::TypeInfo<sqb::traits::RemoveQualifiers<const SQChar *>::kType>::kTypeMask);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestUserPointer)
{
  EXPECT_EQ(sqb::kScriptVarTypeUserPointer, sqb::TypeInfo<SQUserPointer>::kTypeID);
  EXPECT_EQ(sizeof(const SQChar *), sqb::TypeInfo<SQUserPointer>::kTypeSize);
  EXPECT_EQ(_SC('p'), sqb::TypeInfo<SQUserPointer>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<SQUserPointer>::kTypeIsInstance);
  EXPECT_STREQ(_SC("userdata"), sqb::TypeInfo<SQUserPointer>().m_typeName);
}

#if defined(_SQ64)
//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestInt64)
{
  EXPECT_EQ(sqb::kScriptVarTypeInt64, sqb::TypeInfo<int64_t>::kTypeID);
  EXPECT_EQ(sizeof(int64_t), sqb::TypeInfo<int64_t>::kTypeSize);
  EXPECT_EQ(_SC('n'), sqb::TypeInfo<int64_t>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<int64_t>::kTypeIsInstance);
  EXPECT_STREQ(_SC("integer"), sqb::TypeInfo<int64_t>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestUInt64)
{
  EXPECT_EQ(sqb::kScriptVarTypeUInt64, sqb::TypeInfo<uint64_t>::kTypeID);
  EXPECT_EQ(sizeof(uint64_t), sqb::TypeInfo<uint64_t>::kTypeSize);
  EXPECT_EQ(_SC('n'), sqb::TypeInfo<uint64_t>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<uint64_t>::kTypeIsInstance);
  EXPECT_STREQ(_SC("integer"), sqb::TypeInfo<uint64_t>().m_typeName);
}
#endif // defined(_SQ64)

#if defined(SQUSEDOUBLE)
//----------------------------------------------------------------------------------------------------------------------
TEST(TypeInfoTest, TestDouble)
{
  EXPECT_EQ(sqb::kScriptVarTypeDouble, sqb::TypeInfo<double>::kTypeID);
  EXPECT_EQ(sizeof(double), sqb::TypeInfo<double>::kTypeSize);
  EXPECT_EQ(_SC('n'), sqb::TypeInfo<double>::kTypeMask);
  EXPECT_EQ(SQFalse, sqb::TypeInfo<double>::kTypeIsInstance);
  EXPECT_STREQ(_SC("double"), sqb::TypeInfo<double>().m_typeName);
}
#endif // defined(SQUSEDOUBLE)
